#include "POVMesh.h"

#include <QFutureWatcher>
#include <QGLWidget>
#include <QtConcurrentMap>
#include <QTime>

#include <math.h>
#include "POVObject.h"
#include "../geom/Ray.h"
#include "../geom/Vector3D.h"
#include "../../util/ConnectedTupleSet.h"

using namespace POVGeom;


// TODO esta media no es buena, porque en una arista con angulo de 90º sale normal 0,
// y en ángulos mayores salen normales negativas
Vector3D POVMesh::calculateNormalsAverage(const vector<Vector3D>& normals)
{
    int numNormals = normals.size();
    Vector3D sum(0, 0,0);

    for(int i=0;i<numNormals;++i)
    {
        const Vector3D &p = normals[i];
        sum.dx += p.dx;
        sum.dy += p.dy;
        sum.dz += p.dz;
    }

	sum.dx/=numNormals;
	sum.dy/=numNormals;
	sum.dz/=numNormals;

    return sum.getNormalized();
}

void POVMesh::generateNormals()
{
    int numTris = m_mesh->getNumberOfTris();

	// We find the different blocks of connected triangles, so that we can generate
	// the normals independently for each block
	vector< util::ConnectedTupleSet<int,3> > trisBlocks;
	trisBlocks.push_back(util::ConnectedTupleSet<int,3>());
	trisBlocks[0].insertTuple(m_mesh->triangles()[0]);
	vector<int> connectedBlocks;
	connectedBlocks.reserve(100);

	for(int i=1; i<numTris; ++i)
	{
		connectedBlocks.clear();

		const NTuple<int,3> &triangle = m_mesh->triangles()[i]; 
		int numBlocks = trisBlocks.size();

		for(int j=/*0*/numBlocks-1; j<numBlocks;++j) // XXX leaving j initally to 0 would make it check
			if(trisBlocks[j].checkConnection(triangle)) // connections with all detected blocks. Now it just checks the last one. 
				connectedBlocks.push_back(j);

		if(connectedBlocks.size() == 0)
		{// We create a new block
			util::ConnectedTupleSet<int,3> block;
			block.insertTuple(triangle);
			trisBlocks.push_back(block);
		}
		else if(connectedBlocks.size() == 1)
		{
			int block = connectedBlocks[0];
			trisBlocks[block].insertTuple(triangle);
		}
		else
		{
			// We gotta create a block that is the sum of all connected blocks
			util::ConnectedTupleSet<int,3> fusedBlock;
			// We go in the reversed order because if you remove one of the first blocks, the vector indices will change
			for(int block=connectedBlocks.size()-1; block>=0;block--)
			{	
				fusedBlock+=trisBlocks[connectedBlocks[block]];
				// Remove the old blocks from the vector
				trisBlocks.erase(trisBlocks.begin()+block);
			}
			trisBlocks.push_back(fusedBlock);
		}
	}

	for(unsigned int i=0; i<trisBlocks.size();++i)
		generateNormalsForBlock(trisBlocks[i].getTuples());

}

template <int size> bool isAdyacent(NTuple<int,size> t1, NTuple<int,size>t2)
{
	for(int i=0;i<size;i++)
		for(int j=0;j<size;j++)
			if(t1[i] == t2[j])
				return true;
	return false;
}

void POVMesh::generateNormalsForBlock(const vector<NTuple<int,3> > & trisBlock)
{
    // First we find adyacent faces for each face.
    // Note: this is O(n*n)
    int numTris = trisBlock.size();

	map<int, Vector3D> calculatedNormals; // Normals calculated for points

    vector<Triplet<int> > adyacentFaces[numTris];

    for(int i = 0; i<numTris;++i)
        for(int j = 0; j<numTris;++j)
            if(i!=j && isAdyacent(trisBlock[i], trisBlock[j]) )//Note: We obviate the faces with which a face shares only one vertex
			{
				Triplet<int> t(trisBlock[j]);
				adyacentFaces[i].push_back(t);
			}

    // We calculate this faces normal, and for each vertex without its normals
    // calculated, we calculate the normal of the adyacent faces. We then 
    // assign that normal to the vertex in this face, and in the vertex of the 
    // adyacent faces.
    
    int j;

    for(int i=0; i<numTris;++i) // For each face
    {
		Triplet<MeshPoint> tp;
		for(int n=0;n<3;++n)
			tp[n] = m_mesh->getPoint(trisBlock[i][n]);
        Triangle thisTri(tp);
        Vector3D thisNormal = thisTri.getNormal();
			
		// For each of the face vertices
        for(j=0;j<3;++j) 
        {
            vector <Vector3D> adyacentNormals; // We find its neighbours and calculate their normals average
            adyacentNormals.push_back(thisNormal);

            int vertsAdyacent = adyacentFaces[i].size();
            for(int k=0;k<vertsAdyacent;++k)
			{
				if(	adyacentFaces[i][k][0] == j || adyacentFaces[i][k][1] == j || adyacentFaces[i][k][2] == j )
				{
                    // We add to the normals list the normal of every face listed 
                    //as adyacent to this face, that contains this precise vertex
					Triplet<MeshPoint> t;
					for(int n=0;n<3;++n)
						t[n] = m_mesh->getPoint(adyacentFaces[i][k][n]);
                    adyacentNormals.push_back(Triangle(t).getNormal());
                }
			}
            Vector3D normal = calculateNormalsAverage(adyacentNormals);

			calculatedNormals[trisBlock[i][j]] = normal.getNormalized();
        }
    }
	map<int,Vector3D>::const_iterator iter;
	map<int,Vector3D>::const_iterator end = calculatedNormals.end();
	for(iter = calculatedNormals.begin(); iter!=end;iter++)
	{
		int point = (*iter).first;
		Vector3D n = (*iter).second;
		MeshPoint op = m_mesh->getPoint(point);
		op.n = n;
		m_mesh->changePoint(point,op);
	}
}

